#ifndef HILANG_PARSER_HPP
#define HILANG_PARSER_HPP

#include "list.hpp"
#include "buffer.hpp"
#include "tokenizer.hpp"
#include "errmsg.hpp"

struct AstNode;
struct CodeGenNode;
struct ImportTableEntry;

enum NodeType {
    NodeTypeRoot,
    NodeTypeRootExportDecl,
    NodeTypeFnProto,
    NodeTypeFnDef,
    NodeTypeFnDecl,
    NodeTypeParamDecl,
    NodeTypeType,
    NodeTypeBlock,
    NodeTypeExternBlock,
    NodeTypeDirective,
    NodeTypeReturnExpr,
    NodeTypeBinOpExpr,
    NodeTypeCastExpr,
    NodeTypeNumberLiteral,
    NodeTypeStringLiteral,
    NodeTypeSymbol,
    NodeTypePrefixOpExpr,
    NodeTypeFnCallExpr,
    NodeTypeUse,
};

struct AstNodeRoot {
    HiLangList<AstNode *> top_level_decls;
};

enum FnProtoVisibMod {
    FnProtoVisibModPrivate,
    FnProtoVisibModPub,
    FnProtoVisibModExport,
};

struct AstNodeFnProto {
    HiLangList<AstNode *> *directives;
    FnProtoVisibMod visib_mod;
    Buf name;
    HiLangList<AstNode *> params;
    AstNode *return_type;
};

struct AstNodeFnDef {
    AstNode *fn_proto;
    AstNode *body;
};

struct AstNodeFnDecl {
    AstNode *fn_proto;
};

struct AstNodeParamDecl {
    Buf name;
    AstNode *type;
};

enum AstNodeTypeType {
    AstNodeTypeTypePrimitive,
    AstNodeTypeTypePointer,
};

struct AstNodeType {
    AstNodeTypeType type;
    Buf primitive_name;
    AstNode *child_type;
    bool is_const;
};

struct AstNodeBlock {
    HiLangList<AstNode *> statements;
};

struct AstNodeReturnExpr {
    // might be null in case of return void;
    AstNode *expr;
};

enum BinOpType {
    BinOpTypeInvalid,
    // TODO: include assignment?
    BinOpTypeBoolOr,
    BinOpTypeBoolAnd,
    BinOpTypeCmpEq,
    BinOpTypeCmpNotEq,
    BinOpTypeCmpLessThan,
    BinOpTypeCmpGreaterThan,
    BinOpTypeCmpLessOrEq,
    BinOpTypeCmpGreaterOrEq,
    BinOpTypeBinOr,
    BinOpTypeBinXor,
    BinOpTypeBinAnd,
    BinOpTypeBitShiftLeft,
    BinOpTypeBitShiftRight,
    BinOpTypeAdd,
    BinOpTypeSub,
    BinOpTypeMult,
    BinOpTypeDiv,
    BinOpTypeMod,
};

struct AstNodeBinOpExpr {
    AstNode *op1;
    BinOpType bin_op;
    AstNode *op2;
};

struct AstNodeFnCallExpr {
    AstNode *fn_ref_expr;
    HiLangList<AstNode *> params;
};

struct AstNodeExternBlock {
    HiLangList<AstNode *> *directives;
    HiLangList<AstNode *> fn_decls;
};

struct AstNodeDirective {
    Buf name;
    Buf param;
};

struct AstNodeRootExportDecl {
    Buf type;
    Buf name;
    HiLangList<AstNode *> *directives;
};

struct AstNodeCastExpr {
    AstNode *prefix_op_expr;
    // if type is non-null, do cast, otherwise nothing
    AstNode *type;
};

enum PrefixOp {
    PrefixOpInvalid,
    PrefixOpBoolNot,
    PrefixOpBinNot,
    PrefixOpNegation,
};

struct AstNodePrefixOpExpr {
    PrefixOp prefix_op;
    AstNode *primary_expr;
};

struct AstNodeUse {
    Buf path;
    HiLangList<AstNode *> *directives;
};

struct AstNode {
    enum NodeType type;
    int line;
    int column;
    CodeGenNode *codegen_node;
    ImportTableEntry *owner;
    union {
        AstNodeRoot root;
        AstNodeRootExportDecl root_export_decl;
        AstNodeFnDef fn_def;
        AstNodeFnDecl fn_decl;
        AstNodeFnProto fn_proto;
        AstNodeType type;
        AstNodeParamDecl param_decl;
        AstNodeBlock block;
        AstNodeReturnExpr return_expr;
        AstNodeBinOpExpr bin_op_expr;
        AstNodeExternBlock extern_block;
        AstNodeDirective directive;
        AstNodeCastExpr cast_expr;
        AstNodePrefixOpExpr prefix_op_expr;
        AstNodeFnCallExpr fn_call_expr;
        AstNodeUse use;
        Buf number;
        Buf string;
        Buf symbol;
    } data;
};

__attribute__ ((format (printf, 2, 3)))
void ast_token_error(Token *token, const char *format, ...);


// This function is provided by generated code, generated by parsergen.cpp
 AstNode * ast_parse(Buf *buf, HiLangList<Token> *tokens, ImportTableEntry *owner, ErrColor err_color);

const char *node_type_str(NodeType node_type);

void ast_print(AstNode *node, int indent);

#endif